from .single import SingletonMeta
import settings
from fastapi import HTTPException
from fastapi import status
from fastapi import UploadFile
import uuid
from loguru import logger
from datetime import timedelta
import asyncio,base64
import alibabacloud_oss_v2 as oss
from alibabacloud_oss_v2 import Client,PresignResult,credentials
from alibabacloud_oss_v2.models import GetObjectRequest

import os
from asgiref.sync import sync_to_async

class AliyunOss(metaclass=SingletonMeta):
    def __init__(self, region, access_key_id=None, access_key_secret=None):
        if access_key_id and access_key_secret:
            credentials_provider = credentials.StaticCredentialsProvider(
                access_key_id, access_key_secret
            )
        else:
            credentials_provider = credentials.EnvironmentVariableCredentialsProvider()

        # 配置客户端
        self.cfg = oss.config.load_default()
        self.cfg.credentials_provider = credentials_provider
        self.cfg.region = region
        self.client = oss.Client(self.cfg)
        
    async def generate_presigned_url(self, method, bucket_name, object_key, expiration=timedelta(hours=1)):
        """
        生成预签名URL。
        
        :param method: HTTP方法，例如 "GET" 或 "PUT"
        :param bucket_name: 存储空间名称
        :param object_key: 对象键名
        :param expiration: 预签名URL的有效期，默认为1小时
        :return: 预签名URL
        """
        try:
            async_presign = sync_to_async(self.client.presign)
            presign_result = await async_presign(
                method, bucket_name, object_key, expiration=expiration.total_seconds()
            )
            return {
                "url": presign_result.url,
                "method": presign_result.method,
                "signed_headers": presign_result.signed_headers,
            }
        except Exception as e:
            print(f"生成预签名URL时发生错误: {e}")
            return None
        
    async def list_objects(self, bucket_name, prefix=None, delimiter=None):
        """
        列举存储空间中的对象。
        
        :param bucket_name: 存储空间名称
        :param prefix: 可选，列举以指定前缀开头的对象
        :param delimiter: 可选，用于分组对象的分隔符
        :return: 对象列表
        """
        try:
            async_list_objects = sync_to_async(oss.models.ListObjectsRequest)
            request = await async_list_objects(bucket=bucket_name)
            if prefix:
                request.prefix = prefix
            if delimiter:
                request.delimiter = delimiter

            paginator = self.client.list_objects_paginator(request)
            objects = []
            for page in paginator:
                for obj in page.contents:
                    objects.append({"key": obj.key, "size": obj.size, "last_modified": obj.last_modified})
            return objects
        except Exception as e:
            print(f"列举对象时发生错误: {e}")
            return None

    async def oss_upload_image(self,file: UploadFile, max_size: int=1024*1024) -> str|None:
        """
        上传到服务器再转发到阿里云
        """
        if file.size > max_size:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f'文件不能超过{max_size}')
        # 获取文件拓展名
        # abc.png => ['abc', '.png']
        extension = os.path.splitext(file.filename)[1]
        if extension not in ['.jpg', '.jpeg', '.png']:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='请上传正确格式的图片！')
        filename = f"{uuid.uuid4().hex}{extension}"
        filedata = await file.read()
        try:
            # callback = base64.b64encode(
            #     str('{"callbackUrl":"http://example.com/callback","callbackBody":"bucket=${bucket}&object=${object}"}').encode()
            # ).decode()
            # callback_var = base64.b64encode('{"x:var1":"value1","x:var2":"value2"}'.encode()).decode()

            async_put_object = sync_to_async(self.client.put_object)
            result=await async_put_object(
                bucket=settings.oss_bucket,
                key=filename,
                body=filedata,
                # callback=callback,
                # callback_var=callback_var,
            )
            if result.status == 200:
                return filename
            else:
                logger.error(f"上传文件到oss失败: {result.resp.text}")
                return None
        except Exception as e:
            logger.error(f"上传文件到oss失败: {e}")
            return None
        
    async def oss_presign_upload_url(self,filename:str) -> dict|None:
        """
        这个方法是oss上的资源增加签名,用于客户端直接上传文件.不通过服务器中转.
        filename: 文件名,要求必须包含文件拓展名
        return: 签名信息,包含access_key_id,access_key_secret,security_token,expiration,upload_url,object_key,max_size
        """
        extension = os.path.splitext(filename)[1].lower()  # 统一转为小写（如.PNG -> .png）
        if extension not in ['.jpg', '.jpeg', '.png']:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="仅支持.jpg、.jpeg、.png格式"
            )
        unique_filename = f"{uuid.uuid4().hex}{extension}"
        object_key = f"images/{unique_filename}" 
        try:
            # 3.1 调用阿里云STS接口获取临时令牌
            sts_request = AssumeRoleRequest()
            sts_request.set_RoleArn(self.settings.oss_sts_role_arn)  # STS角色ARN
            sts_request.set_RoleSessionName(f"upload-session-{uuid.uuid4().hex[:10]}")  # 会话名
            sts_request.set_DurationSeconds(300)  # 凭证有效期（5分钟，避免长期有效）
            
            # 3.2 发送请求并解析响应
            response = await sync_to_async(self.client.get_response)(sts_request) 
            credentials = response.get("Credentials", {})
            return {
                "access_key_id": credentials.get("AccessKeyId"),
                "access_key_secret": credentials.get("AccessKeySecret"),
                "security_token": credentials.get("SecurityToken"),
                "expiration": credentials.get("Expiration"),  # 凭证过期时间
                "upload_url": f"https://{self.settings.oss_bucket}.{self.settings.oss_endpoint}/{object_key}",  # OSS直传URL
                "object_key": object_key,  # 最终存储的文件名（用于后续访问）
                "max_size": max_size  # 客户端需自行校验文件大小（双重保障）
            }
        except Exception as e:
            logger.error(f"生成OSS直传凭证失败: {e}")
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="获取上传权限失败"
            )
        return None
     
    async def oss_delete_object(self,file_url:str) -> bool:
        """
        这个方法是删除oss上的资源
        file_url: oss上的资源路径
        return: 是否删除成功
        """
        try:
            async_delete_object = sync_to_async(self.client.delete_object)
            await async_delete_object(
                bucket=settings.oss_bucket,
                key=file_url,
            )
            return True
        except Exception as e:
            logger.error(f"删除oss资源失败: {e}")
            return False